#include <cubefs/api/api.hpp>
#include <natives.h>
#include <decls.hpp>
#include <exceptions.hpp>

#include <cubefs/core/tools/types/dispatcher.hpp>

#define CUBEFS_SOURCE "list.hpp:" CUBEFS_AT

using namespace cubefs::api;
using namespace cubefs::tools;

using namespace std;

template <typename Functor>
void DispatchTypedList(TypedListBase *list, Functor &functor) {
    if (ListOfTyped::MyType() == list->type()) {
        ListOfTyped* tl = static_cast<ListOfTyped*>(list);
        functor(tl);
    }
    else if (StaticListOfTyped::MyType() == list->type()) {
        StaticListOfTyped* tl = static_cast<StaticListOfTyped*>(list);
        functor(tl);
    }
    else if (ListOfTypes::MyType() == list->type()) {
        ListOfTypes* tl = static_cast<ListOfTypes*>(list);
        functor(tl);
    }
    else {
        throw new TypeCastException(CUBEFS_SOURCE, "Unrecognized list type: "+list->type()->name(), list->type(), NULL);
    }
}




JNIEXPORT jlong JNICALL METADATA1(TypedListBase, newList) (JNIEnv *env, jclass, jint code) {
    CUBEFS_EXC_BEGIN
    TypedListBase* list = NULL;

    switch (code) {
        case 0 : list = new ListOfTyped(); break;
        case 1 : list = new ListOfTypes(); break;
        case 2 : list = new ListOfBigInt(); break;
        case 3 : list = new DeletableListOfTyped(); break;
        case 4 : list = new StaticListOfTyped(); break;
        default: throw CubefsException(CUBEFS_SOURCE, "Unrecognized TypedList code", code);
    }

    return Ptr2Handle(list);
    CUBEFS_EXC_HANDLE
    return 0;
}


JNIEXPORT jint JNICALL METADATA1(TypedListBase, size) (JNIEnv *env, jclass, jlong handle) {
    CUBEFS_EXC_BEGIN

    TypedListBase* list = Handle2Ptr<TypedListBase>(handle);
    return list->size();

    CUBEFS_EXC_HANDLE
    return 0;
}


JNIEXPORT void JNICALL METADATA1(TypedListBase, Clear) (JNIEnv *env, jclass, jlong handle) {
    CUBEFS_EXC_BEGIN

    TypedListBase* list = Handle2Ptr<TypedListBase>(handle);
    list->Clear();

    CUBEFS_EXC_HANDLE
}


JNIEXPORT void JNICALL METADATA1(TypedListBase, Remove) (JNIEnv *env, jclass, jlong handle, jint index) {
    CUBEFS_EXC_BEGIN

    TypedListBase* list = Handle2Ptr<TypedListBase>(handle);
    list->Remove(index);

    CUBEFS_EXC_HANDLE
}

JNIEXPORT void JNICALL METADATA1(TypedListBase, RemoveAll) (JNIEnv *env, jclass, jlong handle, jint from, jint to) {
    CUBEFS_EXC_BEGIN

    TypedListBase* list = Handle2Ptr<TypedListBase>(handle);
    list->RemoveAll(from, to);

    CUBEFS_EXC_HANDLE
}


struct GetTypedElementFn {
    jlong handle_;
    jint idx_;

    GetTypedElementFn(jint idx): idx_(idx){}

    template <typename List>
    void operator()(List *list) {
        handle_ = Ptr2Handle(list->Get(idx_));
    }
};

JNIEXPORT jlong JNICALL METADATA1(ListOfTypedBase, GetTypedElement) (JNIEnv *env, jclass, jlong handle, jint index) {
    CUBEFS_EXC_BEGIN

    TypedListBase* list = Handle2Ptr<TypedListBase>(handle);

    GetTypedElementFn fn(index);

    DispatchTypedList(list, fn);

    return fn.handle_;

    CUBEFS_EXC_HANDLE
    return 0;
}

struct SetTypedElementFn {
    jlong handle_;
    jint idx_;

    SetTypedElementFn(jint idx, jlong handle): idx_(idx), handle_(handle) {}

    template <typename List>
    void operator()(List *list) {
        typedef typename List::Element Element;

        const Element e = (Element)(handle_);

        list->Set(idx_, e);
    }
};

JNIEXPORT void JNICALL METADATA1(ListOfTypedBase, SetTypedElement) (JNIEnv *env, jclass, jlong handle, jint index, jlong element) {
    CUBEFS_EXC_BEGIN

    TypedListBase* list = Handle2Ptr<TypedListBase>(handle);

    SetTypedElementFn fn(index, element);

    DispatchTypedList(list, fn);

    CUBEFS_EXC_HANDLE
}


struct AddTypedElementFn {
    jlong handle_;
    jint idx_;

    AddTypedElementFn(jint idx, jlong handle): idx_(idx), handle_(handle) {}

    template <typename List>
    void operator()(List *list) {
        typedef typename List::Element Element;

        const Element e = (Element)(handle_);

        list->Insert(idx_, e);
    }
};


JNIEXPORT void JNICALL METADATA1(ListOfTypedBase, AddTypedElement) (JNIEnv *env, jclass, jlong handle, jint index, jlong element) {
    CUBEFS_EXC_BEGIN

    TypedListBase* list = Handle2Ptr<TypedListBase>(handle);

    AddTypedElementFn fn(index, element);

    DispatchTypedList(list, fn);

    CUBEFS_EXC_HANDLE
}


#undef CUBEFS_SOURCE
